package com.sh.data.engine.domain.dataquality.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
import com.sh.data.engine.domain.dataquality.model.domain.DataQualityTableDetail;
import com.sh.data.engine.domain.dataquality.model.domain.DataQualityTableDetail.TimeStatus;
import com.sh.data.engine.domain.dataquality.service.DataQualityJobRuleFieldService;
import com.sh.data.engine.domain.dataquality.service.DataQualityJobRuleService;
import com.sh.data.engine.domain.dataquality.service.DataQualityRuleService;
import com.sh.data.engine.domain.dataquality.service.DataQualityRuleTemplateService;
import com.sh.data.engine.repository.dao.base.EngineBaseModel;
import com.sh.data.engine.repository.dao.dataquality.DataQualityJobRuleMapper;
import com.sh.data.engine.repository.dao.dataquality.entity.DataQualityJobRuleEntity;
import com.sh.data.engine.repository.dao.dataquality.entity.DataQualityJobRuleFieldEntity;
import com.sh.data.engine.repository.dao.dataquality.entity.DataQualityRuleEntity;
import com.sh.data.engine.repository.dao.dataquality.entity.DataQualityRuleTemplateEntity;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author tinglan.ys
 * @description:
 * @date 2022/8/26 下午3:38
 */
@Service
public class DataQualityJobRuleServiceImpl
    extends ServiceImpl<DataQualityJobRuleMapper, DataQualityJobRuleEntity>
    implements DataQualityJobRuleService {

    @Lazy
    @Autowired
    private DataQualityRuleService dataQualityRuleService;

    @Lazy
    @Autowired
    private DataQualityRuleTemplateService dataQualityRuleTemplateService;

    @Lazy
    @Autowired
    private DataQualityJobRuleFieldService dataQualityJobRuleFieldService;

    private Cache<Long, DataQualityRuleTemplateEntity> ruleTemplateCache = CacheBuilder.newBuilder()
        .expireAfterWrite(1, TimeUnit.HOURS)
        .build();

    @Override
    public DataQualityTableDetail getDataQualityByTableId(Long tableId) {
        List<DataQualityRuleEntity> ruleEntities = dataQualityRuleService.list(
            new LambdaQueryWrapper<>(DataQualityRuleEntity.class).eq(DataQualityRuleEntity::getTblId, tableId));

        if (CollectionUtils.isEmpty(ruleEntities)) {
            // 没有配置规则的表，返回null
            return null;
        }

        // 构建返回记录
        DataQualityTableDetail result = new DataQualityTableDetail();
        result.setTableName(ruleEntities.get(0).getTblName());
        result.setTableId(ruleEntities.get(0).getTblId());
        if (!populateTableDetails(result, ruleEntities)) {
            // 该表涉及的所有规则都没有执行过
            return result;
        }

        // 记录规则执行详情
        List<DataQualityTableDetail.DataQualityRuleDetail> ruleDetails = Lists.newArrayList();
        for (DataQualityRuleEntity ruleEntity : ruleEntities) {
            ruleDetails.add(buildRuleDetails(ruleEntity));
        }
        result.setRuleDetails(ruleDetails);

        return result;
    }

    private boolean populateTableDetails(DataQualityTableDetail result, List<DataQualityRuleEntity> ruleEntities) {
        // 提取规则id
        List<Long> ruleIds = ruleEntities.stream().map(EngineBaseModel::getId).collect(Collectors.toList());

        // 获取最近该表涉及的所有rule的最近10次任务执行信息
        List<DataQualityJobRuleEntity> jobRuleEntities = this.list(
            new LambdaQueryWrapper<>(DataQualityJobRuleEntity.class)
                .in(DataQualityJobRuleEntity::getRuleId, ruleIds)
                .orderByDesc(DataQualityJobRuleEntity::getId)
                .last("limit 10"));

        if (CollectionUtils.isEmpty(jobRuleEntities)) {
            return false;
        }

        List<TimeStatus> timeStatuses = jobRuleEntities.stream().map(this::createTimeStatus).collect(Collectors.toList());
        double averageTime = timeStatuses.stream().mapToDouble(TimeStatus::getUsedTime).average().orElse(0.0);

        result.setNowStatus(jobRuleEntities.get(0).getStatus());
        result.setAverageTime(averageTime);
        result.setTimeStatus(timeStatuses);
        result.setNowTime(timeStatuses.get(0).getUsedTime());
        result.setLastTime(jobRuleEntities.get(0).getEndTime());

        return true;
    }

    private DataQualityTableDetail.DataQualityRuleDetail buildRuleDetails(DataQualityRuleEntity ruleEntity) {
        DataQualityTableDetail.DataQualityRuleDetail result = new DataQualityTableDetail.DataQualityRuleDetail();
        result.setRuleName(ruleEntity.getRuleName());

        DataQualityRuleTemplateEntity ruleTemplate = getRuleTemplate(ruleEntity.getTemplateId());
        if (ruleTemplate != null) {
            result.setTemplateName(ruleTemplate.getName());
        }
        result.setFieldNames(ruleEntity.getFieldNames());

        // 获取指定规则的最近10次执行记录
        List<DataQualityJobRuleEntity> jobRuleEntities = this.list(
            new LambdaQueryWrapper<>(DataQualityJobRuleEntity.class)
                .eq(DataQualityJobRuleEntity::getRuleId, ruleEntity.getId())
                .orderByDesc(DataQualityJobRuleEntity::getId)
                .last("limit 10"));
        if (CollectionUtils.isEmpty(jobRuleEntities)) {
            return result;
        }

        DataQualityJobRuleEntity lastJobRuleEntity = jobRuleEntities.get(0);
        result.setLastStartTime(lastJobRuleEntity.getStartTime());
        result.setLastEndTime(lastJobRuleEntity.getEndTime());
        result.setLastStatus(lastJobRuleEntity.getStatus());
        result.setTimeStatus(jobRuleEntities.stream().map(this::createTimeStatus).collect(Collectors.toList()));

        List<DataQualityTableDetail.DataQualityRuleJobDetail> ruleJobList = Lists.newArrayList();
        for (DataQualityJobRuleEntity jobRuleEntity : jobRuleEntities) {
            DataQualityTableDetail.DataQualityRuleJobDetail ruleJob = new DataQualityTableDetail.DataQualityRuleJobDetail();
            ruleJob.setDataQualityJobId(jobRuleEntity.getDataqualityJobId());
            ruleJob.setStartTime(jobRuleEntity.getStartTime());
            ruleJob.setEndTime(jobRuleEntity.getEndTime());
            ruleJob.setStatus(jobRuleEntity.getStatus());
            if (ruleJob.getStatus() == 2 || ruleJob.getStatus() == 3) {
                // 执行失败的规则需要额外获取告警时间和内容
                // 获取告警的字段
                List<DataQualityJobRuleFieldEntity> jobRuleFieldEntities =
                    dataQualityJobRuleFieldService.list(
                        new LambdaQueryWrapper<DataQualityJobRuleFieldEntity>()
                            .eq(DataQualityJobRuleFieldEntity::getDataqualityJobRuleId, jobRuleEntity.getId())
                            .in(DataQualityJobRuleFieldEntity::getStatus, Lists.newArrayList(2, 3)));
                if (CollectionUtils.isNotEmpty(jobRuleFieldEntities)) {
                    DataQualityJobRuleFieldEntity firstWarnRuleField = jobRuleFieldEntities.get(0);
                    ruleJob.setWarnTime(firstWarnRuleField.getCreateTime());
                    ruleJob.setWarnContent(firstWarnRuleField.getWarnContent());
                }
            }

            ruleJobList.add(ruleJob);
        }
        result.setHistoryRecords(ruleJobList);

        return result;
    }

    private TimeStatus createTimeStatus(DataQualityJobRuleEntity e) {
        TimeStatus timeStatus = new TimeStatus();
        timeStatus.setStatus(e.getStatus().equals(3) ? 2 : e.getStatus());
        timeStatus.setUsedTime(e.getStatus().equals(0) ? 0.0 : (e.getEndTime().getTime() - e.getStartTime().getTime()));
        return timeStatus;
    }

    private DataQualityRuleTemplateEntity getRuleTemplate(Long templateId) {
        try {
            return ruleTemplateCache.get(templateId, () -> dataQualityRuleTemplateService.getById(templateId));
        } catch (Exception e) {
            // Handle exception
            return null;
        }
    }
}
